Могу ли я иметь словарь с одноименными ключами? - PullRequest
12 голосов
/ 25 июля 2011

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

Например

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

Ответы [ 6 ]

14 голосов
/ 25 июля 2011

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

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)

Я не проверял это.

11 голосов
/ 25 июля 2011

Вы также можете попробовать 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-запроса, которые могут иметьодноименные ключи.

6 голосов
/ 25 июля 2011

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

myDict = {'key': []}

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

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

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

myDict = {}
myDict.setdefault(newKey, []).append(value)
2 голосов
/ 21 декабря 2015

Я недоволен всеми предлагаемыми решениями, так что это мое решение.Это для 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: []})

КОД

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.Все значения, передаваемые в конструктор или назначаемые непосредственно ключу, должны быть итеративными.

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

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

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

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

1 голос
/ 08 марта 2014

Это идеальное место для использования объекта 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 для указания из стандартной библиотеки потребует значительных изменений в синтаксисе при назначении значений и может стать довольно запутанным. Я никогда не использовал Multidict, но это также выглядит как существенное изменение в способах назначения. Используя этот метод, вы просто предполагаете, что в словаре уже может быть значение, связанное с этим ключом, и немного модифицируете свой оператор присваивания, используя оператор «+ =» при назначении значений ключа.

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

mydict = defaultdict(lambda: None)
1 голос
/ 09 февраля 2014
def toMultiDict(items):
    def insertMulti(d, kv):
        k, v = kv
        d.setdefault(k, []).append(v)
        return d
    return reduce(insertMulti, [{}] + items)

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...