Как реализовать dict с абстрактными базовыми классами в Python? [дубликат]
этот вопрос уже есть ответ здесь:
- как" идеально " переопределить дикт? 5 ответов
Я попытался реализовать сопоставление в Python, используя абстрактный базовый класс MutableMapping, но я получил ошибку при создании экземпляра. Как я могу сделать рабочую версию этого словаря, которая будет эмулировать встроенный 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
(концепция, с которой я хорошо знаком).
5 ответов
как бы я реализовал dict с абстрактными базовыми классами?
хороший ответ продемонстрирует, как сделать эту работу, в частности без подклассов dict.
вот сообщение об ошибке: TypeError: Can't instantiate abstract class D with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__
оказывается, что нужно реализовать их, чтобы использовать абстрактный базовый класс (ABC),MutableMapping
.
реализация
поэтому я реализую отображение, которое работает как дикт в большинстве уважает, который использует ссылку атрибута объекта dict для сопоставления. (Делегирование не совпадает с наследованием, поэтому мы просто делегируем экземпляр __dict__
, мы могли бы использовать любое другое специальное отображение, но вы, похоже, не заботитесь об этой части реализации. Имеет смысл сделать это таким образом в Python 2, потому что MutableMapping не имеет __slots__
в Python 2, поэтому вы создаете __dict__
в любом случае. В Python 3 Вы можете полностью избежать диктовки, установив __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]}
и для обеспечения API dict, извлеченный урок заключается в том, что вы всегда можете проверить collections.MutableMapping
:
>>> isinstance(d, collections.MutableMapping)
True
>>> isinstance(dict(), collections.MutableMapping)
True
и хотя dict всегда будет экземпляром MutableMapping из-за регистрации при импорте коллекций, обратное не всегда верно:
>>> isinstance(d, dict)
False
>>> isinstance(d, (dict, collections.MutableMapping))
True
после выполнения этого упражнения мне ясно, что использование абстрактного Базовые классы предоставляют пользователям класса только гарантию стандартного API. В этом случае пользователям, предполагающим объект MutableMapping, будет гарантирован стандартный API для Python.
предостережения:
на 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'
можно замаскировать встроенные методы dict, такие как 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__'
по крайней мере
вам нужно реализовать в своем подклассе все абстрактные методы, которые вы наследуете от 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, красный черный) и способ создания вашего словаря
лучший способ продемонстрировать это без фактического использования dict
в любом месте, вероятно, реализовать что-то мертвое простое, очень отличается от dict
, и не совсем бесполезно. Как отображение фиксированного размера фиксированного размера bytes
для того же фиксированного размера bytes
. (Вы можете использовать это, например, для таблицы маршрутизации-она будет намного компактнее, чем dict
сопоставление распакованных Ключей с распакованными значениями, хотя, очевидно, за счет скорости и гибкости.)
хэш-таблица просто массив (hash, key, value)
ОК. Поскольку весь смысл этого-упаковка данных, мы запихиваем их в struct
, то есть, мы можем просто использовать большой bytearray
для хранения. Чтобы отметить пустой слот, мы устанавливаем его хэш-значение 0
- что означает, что нам нужно "убежать" от любого реального 0
превращается в 1
, что глупо, но проще в код. Мы также будем использовать dumbest possible для простоты.
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'' * 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__
. Тем не менее, лучшее место для поиска -документы, который скажет вам, что если вы реализуете эти пять методов (плюс любые другие методы, требуемые базовыми классами, но, как вы можете видеть из таблицы, их нет), вы получите все остальные методы бесплатно. Вы можете не получить максимально эффективной реализации всех из них, но мы вернемся к что.
во-первых, давайте разберемся с __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
, и, возможно, другие методы.
вся идея абстрактного базового класса заключается в том, что у него есть некоторые члены (чистые виртуальные члены в терминах C++), которые ваш код должен предоставить - в C++ это чистые виртуальные члены и другие виртуальные члены, которые вы мая переопределить.
Python отличается от C++ тем, что все члены всех классов являются виртуальными и могут быть переопределены (и вы можете добавлять члены ко всем классам и экземплярам), но абстрактные базовые классы имеют некоторые необходимые отсутствующие классы. equivent в C++ чисто виртуальные.
получив это, вам просто нужно предоставить отсутствующие члены, чтобы иметь возможность создать экземпляр вашего производного класса.
для примера того, что вы пытаетесь сделать, см. принятый ответ здесь но вместо того, чтобы использовать dict в классе, вам придется предоставить методы, которые он предоставляет самостоятельно.
С MutableMapping
в качестве базового класса, вы должны создать этот метод в свой класс: __delitem__, __getitem__, __iter__, __len__, __setitem__
.
чтобы создать пользовательский класс dict, вы можете получить его из dict:
>>> class D(dict):
... pass
...
>>> d = D()
>>> d
{}