2015-01-29 2 views
0

У меня есть вложенный объект словаря, и я хочу иметь возможность извлекать значения ключей с произвольной глубиной. Я могу сделать это с помощью подклассов dict:Есть ли рекурсивная версия встроенного в dict.get() Python?

>>> class MyDict(dict): 
...  def recursive_get(self, *args, **kwargs): 
...   default = kwargs.get('default') 
...   cursor = self 
...   for a in args: 
...    if cursor is default: break 
...    cursor = cursor.get(a, default) 
...   return cursor 
... 
>>> d = MyDict(foo={'bar': 'baz'}) 
>>> d 
{'foo': {'bar': 'baz'}} 
>>> d.get('foo') 
{'bar': 'baz'} 
>>> d.recursive_get('foo') 
{'bar': 'baz'} 
>>> d.recursive_get('foo', 'bar') 
'baz' 
>>> d.recursive_get('bogus key', default='nonexistent key') 
'nonexistent key' 

Однако, я не хочу иметь подкласс dict, чтобы получить такое поведение. Есть ли встроенный метод, который имеет эквивалентное или подобное поведение? Если нет, существуют ли какие-либо стандартные или внешние модули, обеспечивающие такое поведение?

Я использую Python 2.7 на данный момент, хотя мне было бы интересно узнать о решениях 3.x.

+0

d.get ('foo'). Get ('bar')? – Foon

+0

Похоже, вы довольны функциональностью, которую вы достигли, используя код, опубликованный в вашем вопросе. Есть ли какая-то конкретная причина, по которой вы не хотите подкласса 'dict'? –

+0

@Foon, который не гнездится на произвольную глубину, и он генерирует исключение (вместо того, чтобы возвращать значение по умолчанию), если какой-либо ключ в начале цепи не существует. – jayhendren

ответ

5

очень общий шаблон, чтобы сделать это, чтобы использовать пустой Dict в качестве используемого по умолчанию:

d.get('foo', {}).get('bar') 

Если у вас есть больше, чем пара ключей, вы можете использовать reduce (обратите внимание, что в Python 3 reduce сусло импортироваться: from functools import reduce) применить операцию несколько раз

reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d) 

конечно, вы должны рассмотреть оборачивая в функцию (или метод):

def recursive_get(d, *keys): 
    return reduce(lambda c, k: c.get(k, {}), keys, d) 
+0

Спасибо! Мне было интересно, есть ли такой подход на основе Python; используя пустой dicts по умолчанию 'get()', и использование анонимных функций выглядит как хорошие идиомы. – jayhendren

1

Нет ни одного, о котором я знаю. Тем не менее, вам не нужно подкласс Dict на всех, вы можете просто написать функцию, которая принимает словарь, арг и kwargs и делает то же самое:

def recursive_get(d, *args, **kwargs): 
    default = kwargs.get('default') 
    cursor = d 
    for a in args: 
     if cursor is default: break 
     cursor = recursive_get(cursor, a, default) 
    return cursor 

использовать его, как это

recursive_get(d, 'foo', 'bar') 
0

collections.default_dict будет обрабатывать предоставление значений по умолчанию для несуществующих ключей как минимум.

+0

Я не мог понять, как сделать его рекурсивным. –

+0

Так будет 'dict.get()'. Меня это не касается. – jayhendren

1

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

In [1]: def recursive_get(d, *args, default=None): 
    ...:  if not args: 
    ...:   return d 
    ...:  key, *args = args 
    ...:  return recursive_get(d.get(key, default), *args, default=default) 
    ...: 

Аналогичный код также будет работать в Python 2, но вы должны были бы вернуться к использованию **kwargs, как и в вашем примере. Вам также необходимо использовать индексирование для разложения *args.

В любом случае, нет необходимости в цикле, если вы все равно сделаете функцию рекурсивной.

Вы можете видеть, что приведенный выше код демонстрирует такую ​​же функциональность как существующий метод:

In [2]: d = {'foo': {'bar': 'baz'}} 

In [3]: recursive_get(d, 'foo') 
Out[3]: {'bar': 'baz'} 

In [4]: recursive_get(d, 'foo', 'bar') 
Out[4]: 'baz' 

In [5]: recursive_get(d, 'bogus key', default='nonexistent key') 
Out[5]: 'nonexistent key' 
1

Вы можете использовать defaultdict, чтобы дать вам пустой Dict на недостающие ключи:

from collections import defaultdict 
mydict = defaultdict(dict) 

Этом идет только на один уровень - mydict[missingkey] - пустой dict, mydict[missingkey][missing key] - KeyError. Вы можете добавить столько уровней, сколько необходимо, обернув его больше defaultdict s, например defaultdict(defaultdict(dict)).Вы также можете иметь сокровенные один как другой defaultdict с разумной функцией фабрики для вашего случая использования, например

mydict = defaultdict(defaultdict(lambda: 'big summer blowout')) 

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

def insanity(): 
    return defaultdict(insanity) 

print(insanity()[0][0][0][0]) 
Смежные вопросы