Подтвердить что ты не робот

Могу ли я иметь словарь с одноименными ключами?

Мне нужно иметь словарь, который может иметь одинаковые имена для некоторых ключей и возвращать список значений при ссылке на ключ в этом случае.

Например

print mydict['key']
[1,2,3,4,5,6]
4b9b3361

Ответ 1

Для согласованности вы должны иметь ключи карты словаря для списков (или наборов) значений, из которых некоторые могут быть пустыми. Для этого есть приятная идиома:

from collections import defaultdict
d = defaultdict(set)

d["key"].add(...)

(A defaultdict похож на обычный словарь, но если отсутствует ключ, он вызывается аргументом, который вы передали, когда вы его создавали и использовали результат как значение по умолчанию. Таким образом, это автоматически создаст пустой набор значения, если вы запрашиваете ключ, который еще не присутствует.)


Если вам нужен объект, похожий на словарь (т.е. установить значение d["key"] = ...), вы можете сделать следующее. Но это, вероятно, плохая идея, потому что она идет против обычного синтаксиса Python и, скорее всего, вернется и укусит вас позже. Особенно, если кто-то еще должен поддерживать ваш код.

class Multidict(defaultdict):
    def __init__(self):
        super(Multidict, self).__init__(set)

    def __setitem__(self, key, value):
        self[key].add(value)

Я не тестировал это.

Ответ 2

Вы также можете попробовать paste.util.multidict.MultiDict

$ easy_install Paste

Тогда:

from paste.util.multidict import MultiDict
d = MultiDict()
d.add('a', 1)
d.add('a', 2)
d.add('b', 3)
d.mixed()
>>> {'a': [1, 2], 'b': 3}
d.getall('a')
>>> [1, 2]
d.getall('b')
>>> [3]

Веб-фреймворки, такие как Pylons, используют эту библиотеку для обработки строковых/почтовых данных HTTP-запроса, которые могут иметь одноименные ключи.

Ответ 3

Вы можете использовать:

myDict = {'key': []}

Затем во время выполнения:

if newKey in myDict:
    myDict[newKey].append(value)
else:
    myDict[newKey] = [value]

Отредактировано в соответствии с комментарием @Ben:

myDict = {}
myDict.setdefault(newKey, []).append(value)

Ответ 4

Я не удовлетворен всеми предлагаемыми решениями, так что это мое решение. Это для Python 3. Код ниже.

Примеры

(код ниже)

>>> a = MultiDict({0: [0]})
>>> a
MultiDict({0: [0]})
>>> a[0] = (1, 7)
>>> a
MultiDict({0: [1, 7]})
>>> a.add(0, 2)
>>> a
MultiDict({0: [1, 7, 2]})
>>> a.add(1, 2)
>>> a
MultiDict({0: [1, 7, 2], 1: [2]})
>>> a.getfirst(0)
1
>>> a.getfirst(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 61, in getfirst
  File "<stdin>", line 17, in __getitem__
KeyError: 3
>>> len(a)
2
>>> tuple(a.items())
((0, [1, 7, 2]), (1, [2]))
>>> tuple(a.values())
([1, 7, 2], [2])
>>> a.get(0)
[1, 7, 2]
>>> tuple(a.multiitems())
((0, 1), (0, 7), (0, 2), (1, 2))
>>> tuple(a.multikeys())
(0, 0, 0, 1)
>>> tuple(a.multivalues())
(1, 7, 2, 2)
>>> a.remove(0, 1)
>>> a
MultiDict({0: [7, 2], 1: [2]})
>>> a.remove(3, 5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 53, in remove
  File "<stdin>", line 17, in __getitem__
KeyError: 3
>>> a.remove(0, 5)
Traceback (most recent call last):
  File "<stdin>", line 53, in remove
ValueError: list.remove(x): x not in list

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 56, in remove
ValueError: No element with value 5 for key 0
>>> b = MultiDict({0: [7, 2], 1: [2]})
>>> b == a
True
>>> c = MultiDict(a)
>>> c
MultiDict({0: [7, 2], 1: [2]})
>>> d = MultiDict({0: 0})
Traceback (most recent call last):
  File "<stdin>", line 30, in __init__
TypeError: 'int' object is not iterable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 33, in __init__
TypeError: Values must be iterables, found 'int' for key 0
>>> a.pop(0)
[7, 2]
>>> a
MultiDict({1: [2]})
>>> c.popitem()
(0, [7, 2])
>>> c.setdefault(0, [1])
[1]
>>> c
MultiDict({0: [1], 1: [2]})
>>> c.setdefault(0, [2])
[1]
>>> c
MultiDict({0: [1], 1: [2]})
>>> c.setdefault(3)
[]
>>> c
MultiDict({0: [1], 1: [2], 3: []})
>>> c.getfirst(3)
Traceback (most recent call last):
  File "<stdin>", line 61, in getfirst
IndexError: list index out of range

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 63, in getfirst
IndexError: No values in key 3
>>> c.clear()
>>> c
MultiDict({})
>>> c.update(b)
>>> c
MultiDict({0: [7, 2], 1: [2]})
>>> d = c.copy()
>>> d == c
True
>>> id(d) == id(c)
False
>>> MultiDict.fromkeys((0, 1), [5])
MultiDict({0: [5], 1: [5]})
>>> MultiDict.fromkeys((0, 1))
MultiDict({0: [], 1: []})

CODE

from collections.abc import MutableMapping


class MultiDict(MutableMapping):
    @classmethod
    def fromkeys(cls, seq, value=None, *args, **kwargs):
        if value is None:
            v = []
        else:
            v = value

        return MultiDict(dict.fromkeys(seq, v, *args, **kwargs))


    def __setitem__(self, k, v):
        self._dict[k] = list(v)


    def __getitem__(self, k):
        return self._dict[k]


    def __iter__(self):
        for k in self._dict:
            yield k


    def __init__(self, *args, **kwargs):
        self._dict = dict(*args, **kwargs)

        for k, v in self._dict.items():
            try:
                self._dict[k] = list(v)
            except TypeError:
                err_str = "Values must be iterables, found '{t}' for key {k}"
                raise TypeError(err_str.format(k=k, t=type(v).__name__))


    def __delitem__(self, k):
        del self._dict[k]


    def __len__(self):
        return len(self._dict)


    def add(self, k, v):
        if not k in self:
            self[k] = []

        self[k].append(v)


    def remove(self, k, v):
        try:
            self[k].remove(v)
        except ValueError:
            err_str = "No element with value {v} for key {k}"
            raise ValueError(err_str.format(v=v, k=k))


    def getfirst(self, k):
        try:
            res = self[k][0]
        except IndexError:
            raise IndexError("No values in key {k}".format(k=k))

        return self[k][0]


    def multiitems(self):
        for k, v in self.items():
            for vv in v:
                yield (k, vv)


    def multikeys(self):
        for k, v in self.items():
            for vv in v:
                yield k


    def multivalues(self):
        for v in self.values():
            for vv in v:
                yield vv


    def setdefault(self, k, default=None):
        if default is None:
            def_val = []
        else:
            def_val = default

        if k not in self:
            self[k] = def_val

        return self[k]


    def copy(self):
        return MultiDict(self)


    def __repr__(self):
        body_str = ""

        for k, v in self.items():
            body_str += "{k}: {v}, ".format(k=repr(k), v=repr(v))

        if body_str:
            body_str_true = body_str[:-2]
        else:
            body_str_true = body_str

        return "MultiDict({{{body}}})".format(body=body_str_true)

НЕКОТОРЫЕ ВЕРБОМНЫЕ ОБЪЯСНЕНИЯ

Для простоты конструктор совпадает с dict. Все значения, переданные конструктору или назначенные непосредственно ключу, должны быть итерабельными.

Все значения my MultiDict являются списками, даже если значение равно только одному. Это делается для того, чтобы избежать путаницы.

Я добавил также метод remove для удаления одной записи из MultiDict. Кроме того, я добавил multiitems, который перебирает пару (ключ, значение) по всем значениям словаря. multikeys и multivalues похожи.

АЛЬТЕРНАТИВНЫЕ

Вы также можете использовать aiohttp, WebOp или Werkzeug реализация MultiDict.

Ответ 5

def toMultiDict(items):
    def insertMulti(d, kv):
        k, v = kv
        d.setdefault(k, []).append(v)
        return d
    return reduce(insertMulti, [{}] + items)

должен создать dict из ключа в список значений:

In [28]: toMultiDict(zip([1,2,1], [4,5,6]))
Out[28]: {1: [4, 6], 2: [5]}

Я не мог поместить insertMulti в лямбда, потому что лямбда должна снова вернуть dict.

Ответ 6

Это идеальное место для использования объекта defaultdict из библиотеки коллекций

from collections import defaultdict

mydict = defaultdict(set)
mydict['key'] += set([1,2,3,4])
mydict['key'] += set([4,5,6])

print(mydict['key'])

возвращает [1,2,3,4,5,6]

В случае ссылки на ключ, который не был неявно назначен, возвращается пустой набор.

print(mydict['bad_key'])

возвращает []

Использование setdefault в dict из стандартной библиотеки потребует значительного изменения вашего синтаксиса при назначении значений и может стать довольно грязным. Я никогда не использовал Multidict, но он также выглядит значительным изменением в способах создания заданий. Используя этот метод, вы просто предполагаете, что уже может быть значение, связанное с этим ключом в словаре, и слегка изменить ваш оператор присваивания с помощью оператора "+ =" при назначении значений ключа.

FYI. Я большой поклонник использования NoneType в качестве значения по умолчанию, которое приводит к любому доступу к недопустимому ключу, возвращающему None. В большинстве случаев это ведет себя правильно, включая итерационные и json-дампы, но для вашей конкретной потребности по умолчанию должен быть установлен тип, если вы не хотите включать дубликаты значений, хранящихся в ключе. Затем используйте список. Фактически, в любое время, когда у вас есть гомогенный словарь, значение по умолчанию должно быть такого типа.

mydict = defaultdict(lambda: None)