Я продолжаю возвращаться к этой теме ... Вот еще один вариант. Мне непросто с помощью подкласса dict
добавить метод __hash__
; Практически невозможно избежать проблемы, заключающейся в том, что диктаты изменчивы, и полагать, что они не изменятся, кажется слабой идеей. Поэтому я вместо этого посмотрел на построение отображения на основе встроенного типа, который сам по себе неизменен. хотя tuple
является очевидным выбором, доступ к значениям в нем подразумевает сортировку и деление на части; не проблема, но, похоже, он не использует большую часть возможностей того типа, на котором он построен.
Что, если вы нажмете клавишу, пары значений в frozenset
? Что это потребует, как это будет работать?
Часть 1, вам нужен способ кодирования предметов таким образом, чтобы фрозенцет мог обращаться с ними главным образом по ключам; Я сделаю для этого небольшой подкласс.
import collections
class pair(collections.namedtuple('pair_base', 'key value')):
def __hash__(self):
return hash((self.key, None))
def __eq__(self, other):
if type(self) != type(other):
return NotImplemented
return self.key == other.key
def __repr__(self):
return repr((self.key, self.value))
Это само по себе ставит вас на расстоянии разброса неизменного отображения:
>>> frozenset(pair(k, v) for k, v in enumerate('abcd'))
frozenset([(0, 'a'), (2, 'c'), (1, 'b'), (3, 'd')])
>>> pairs = frozenset(pair(k, v) for k, v in enumerate('abcd'))
>>> pair(2, None) in pairs
True
>>> pair(5, None) in pairs
False
>>> goal = frozenset((pair(2, None),))
>>> pairs & goal
frozenset([(2, None)])
D'oh! К сожалению, когда вы используете операторы множеств и элементы равны, но не один и тот же объект; в итоге возвращаемое значение равно undefined , нам придется пройти еще несколько вращений.
>>> pairs - (pairs - goal)
frozenset([(2, 'c')])
>>> iter(pairs - (pairs - goal)).next().value
'c'
Однако поиск значений таким способом является громоздким и, что еще хуже, создает множество промежуточных наборов; это не будет делать! Мы создадим «поддельную» пару ключ-значение, чтобы обойти ее:
class Thief(object):
def __init__(self, key):
self.key = key
def __hash__(self):
return hash(pair(self.key, None))
def __eq__(self, other):
self.value = other.value
return pair(self.key, None) == other
Что приводит к менее проблематичным:
>>> thief = Thief(2)
>>> thief in pairs
True
>>> thief.value
'c'
Это все глубокая магия; остальное - все это превращает во что-то, что имеет интерфейс как диктовку. Так как мы создаем подклассы из frozenset
, у которого совершенно другой интерфейс, существует довольно много методов; Мы получаем небольшую помощь от collections.Mapping
, но большая часть работы переопределяет методы frozenset
для версий, которые работают как dicts, вместо этого:
class FrozenDict(frozenset, collections.Mapping):
def __new__(cls, seq=()):
return frozenset.__new__(cls, (pair(k, v) for k, v in seq))
def __getitem__(self, key):
thief = Thief(key)
if frozenset.__contains__(self, thief):
return thief.value
raise KeyError(key)
def __eq__(self, other):
if not isinstance(other, FrozenDict):
return dict(self.iteritems()) == other
if len(self) != len(other):
return False
for key, value in self.iteritems():
try:
if value != other[key]:
return False
except KeyError:
return False
return True
def __hash__(self):
return hash(frozenset(self.iteritems()))
def get(self, key, default=None):
thief = Thief(key)
if frozenset.__contains__(self, thief):
return thief.value
return default
def __iter__(self):
for item in frozenset.__iter__(self):
yield item.key
def iteritems(self):
for item in frozenset.__iter__(self):
yield (item.key, item.value)
def iterkeys(self):
for item in frozenset.__iter__(self):
yield item.key
def itervalues(self):
for item in frozenset.__iter__(self):
yield item.value
def __contains__(self, key):
return frozenset.__contains__(self, pair(key, None))
has_key = __contains__
def __repr__(self):
return type(self).__name__ + (', '.join(repr(item) for item in self.iteritems())).join('()')
@classmethod
def fromkeys(cls, keys, value=None):
return cls((key, value) for key in keys)
, что, в конечном счете, отвечает на мой собственный вопрос:
>>> myDict = {}
>>> myDict[FrozenDict(enumerate('ab'))] = 5
>>> FrozenDict(enumerate('ab')) in myDict
True
>>> FrozenDict(enumerate('bc')) in myDict
False
>>> FrozenDict(enumerate('ab', 3)) in myDict
False
>>> myDict[FrozenDict(enumerate('ab'))]
5