2015-03-23 6 views
15

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

# 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)

ответ

10

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

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, то это довольно эффективно.

+1

Спасибо за тайминги! '' {k: d [k] для k в l} '' достаточно читаемо для кого-то с некоторым опытом (и более того, чем немного более сложная версия в моем вопросе), но что-то вроде '' d.intersect (l) '' будет еще лучше: есть словарь, список, и я что-то им делаю, не нужно упоминать k три раза, что не является ни вводом, ни выходом операции. Я знаю, что я жалуюсь на очень высоком уровне :) – Zak

+0

@ Zak, не беспокойтесь, я думаю, что если ключи всегда находятся в dict, и вы даете, возможно, более объяснительные имена для переменных, тогда '{k: d [k] для k в l} 'довольно pythonic –

9

Используйте набор пересекаться на dict.viewkeys() dictionary view:

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} 
+0

это любая проблема '{ключ: d [ключ] для ключа in l} ' – itzMEonTV

+0

@itzmeontv: если вы знаете, что все ключи в' l' находятся в 'd', то это не проблема. Но использование 'd.viewkeys() & l' принимает ** пересечение **, ключи, которые присутствуют как в' d', так и в наборе 'l'. –

+0

oh получил это :) '{key: d [key] для ключа в l, если ключ в d}' получить странно? – itzMEonTV

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 
+0

классная идея, хотя я бы предпочел добавить дополнительный атрибут, чем беспорядок с существующими функциями. что-то вроде '' d.slice (l) ''. Вообще-то, я надеялся, что существует нечто подобное. Более читабельны, чем петли. – Zak

+0

Несомненно, это совершенно противно писать другой метод, или вы можете использовать '__call__'. – kindall

+0

Я бы не сказал, что первое предложение является корунтом вообще. Любой другой код, который использует ваш 'd', будет ожидать' [', чтобы иметь обычную семантику, но они будут изменены. Пример «среза» является сложным. –

1

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} 
4

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

import itertools 

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

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

{1: 2, 3: 4} 
+0

Итак ... это даст мне первые два элемента, не так ли? Являются ли словари в Python 3 упорядоченными в некотором роде? Потому что в противном случае не указывается, какие элементы будут возвращаться, и он может работать только на последовательных, когда я действительно хотел, чтобы они были выбраны с помощью списка ключей. – Zak

+0

@Zak На python 3.6+ словари упорядочены по умолчанию, иначе вам нужно использовать OrderedDict –

+0

. Этот ответ не имеет отношения к вопросу. –

Смежные вопросы