Нарезка словаря

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

# the dictionary
d = {1:2, 3:4, 5:6, 7:8}

# the subset of keys I'm interested in
l = (1,5)

Теперь, в идеале, я хотел бы иметь возможность сделать это:

>>> d[l]
{1:2, 5:6}

... но это не работает, так как он будет искать ключ с именем (1,5). И d[1,5] даже не действителен Python (хотя, похоже, это будет удобно).

Я знаю, что могу сделать это:

>>> dict([(key, value) for key,value in d.iteritems() if key in l])
{1: 2, 5: 6}

или это:

>>> dict([(key, d[key]) for key in l])

который более компактен... но я считаю, что должен быть "лучший" способ сделать это. Не хватает ли более элегантного решения?

(Я использую Python 2.7)

Ответы

Ответ 1

Вы должны выполнять итерацию по кортежу и проверять, находится ли ключ в dict, а не наоборот, если вы не проверяете, существует ли ключ и нет в dict, вы получите ключевую ошибку:

print({k:d[k] for k in l if k in d})

Некоторые тайминги:

 {k:d[k] for k in set(d).intersection(l)}

In [22]: %%timeit                        
l = xrange(100000)
{k:d[k] for k in l}
   ....: 
100 loops, best of 3: 11.5 ms per loop

In [23]: %%timeit                        
l = xrange(100000)
{k:d[k] for k in set(d).intersection(l)}
   ....: 
10 loops, best of 3: 20.4 ms per loop

In [24]: %%timeit                        
l = xrange(100000)
l = set(l)                              
{key: d[key] for key in d.viewkeys() & l}
   ....: 
10 loops, best of 3: 24.7 ms per

In [25]: %%timeit                        

l = xrange(100000)
{k:d[k] for k in l if k in d}
   ....: 
100 loops, best of 3: 17.9 ms per loop

Я не вижу, как {k:d[k] for k in l} не читается или изящно, и если все элементы находятся в d, то это довольно эффективно.

Ответ 2

Используйте набор для пересечения в представлении слова dict.viewkeys():

l = {1, 5}
{key: d[key] for key in d.viewkeys() & l}

Это синтаксис Python 2, в Python 3 используется d.keys().

Это все еще использует цикл, но, по крайней мере, понимание словаря намного читаемо. Использование множества пересечений очень эффективно, даже если d или l велико.

Демо-версия:

>>> d = {1:2, 3:4, 5:6, 7:8}
>>> l = {1, 5}
>>> {key: d[key] for key in d.viewkeys() & l}
{1: 2, 5: 6}

Ответ 3

В Python 3 вы можете использовать itertools islice для dict.items() итератора dict.items()

import itertools

d = {1: 2, 3: 4, 5: 6}

dict(itertools.islice(d.items(), 2))

{1: 2, 3: 4}

Примечание: это решение не учитывает конкретные ключи. Он разрезает по внутреннему порядку d, что в Python 3. 7+ гарантированно будет упорядоченным по вставке.

Ответ 4

Написать dict подкласс, который принимает список ключей в качестве "элемента" и возвращает "срез" словаря:

class SliceableDict(dict):
    default = None
    def __getitem__(self, key):
        if isinstance(key, list):   # use one return statement below
            # uses default value if a key does not exist
            return {k: self.get(k, self.default) for k in key}
            # raises KeyError if a key does not exist
            return {k: self[k] for k in key}
            # omits key if it does not exist
            return {k: self[k] for k in key if k in self}
        return dict.get(self, key)

Применение:

d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d[[1, 5]]   # {1: 2, 5: 6}

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

class SliceableDict(dict):
    def slice(self, *keys):
        return {k: self[k] for k in keys}
        # or one of the others from the first example

d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d.slice(1, 5)     # {1: 2, 5: 6}
keys = 1, 5
d.slice(*keys)    # same

Ответ 5

set intersection и dict comprehension можно использовать здесь

# the dictionary
d = {1:2, 3:4, 5:6, 7:8}

# the subset of keys I'm interested in
l = (1,5)

>>>{key:d[key] for key in set(l) & set(d)}
{1: 2, 5: 6}