Каков питонический способ доступа к вложенным диктовкам без ошибок NoneType

У меня есть глубокий вложенный дикт (декодированный из json, из instagram api). Мой исходный код был таким:

caption = post['caption']['text']

Но это вызовет ошибку NoneType или KeyError, если ключ «заголовок» или ключ «текст» не существует.

Итак, я придумал это:

caption = post.get('caption', {}).get("text")

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

image_url = post.get('images',{}).get('standard_resolution',{}).get('url')

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

Спасибо!


person Kenny Winker    schedule 23.02.2013    source источник
comment
Почему вы не можете просто поймать исключение?   -  person Cairnarvon    schedule 23.02.2013
comment
Я могу. Я думаю, потому что я нажимаю ~ 7 клавиш, я не хотел пытаться / кроме 7 раз.   -  person Kenny Winker    schedule 23.02.2013


Ответы (4)


Самым питоническим способом было бы просто поймать KeyError:

try:
    caption = post['caption']['text']
except KeyError:
    caption = None

Это просто, очевидно и сразу понятно программисту на Python.

person nneonneo    schedule 23.02.2013
comment
Пожалуйста, не размещайте тела try: и except: на той же строке, что и их представления. Это один из PEP 8 "Определенно не соответствует" и сам по себе не питонический. правильно. - person Cairnarvon; 23.02.2013
comment
Исправлено, с извинениями перед PEP 8. - person nneonneo; 23.02.2013
comment
Есть ли способ обобщить это для нескольких ключей? например если я хочу получить caption.text, а также images.standard_resolution.url и user.username и некоторые другие, должен ли я делать блоки try/except? - person Kenny Winker; 23.02.2013
comment
Вы можете определить функцию для получения списка ключей с помощью try/except. - person nneonneo; 23.02.2013

Python 3.4 и более новые версии содержат контекстный менеджер contextlib suppress, который предназначен именно для таких вещей. Подавление определенных ошибок, когда вы заранее знаете, что они могут произойти, и ваш код может с этим справиться.

from contextlib import suppress

sample = {'foo': 'bar'}

with suppress(KeyError):
    print(sample['baz'])

Предотвратит поднятие KeyError.

Таким образом, для доступа к глубоко вложенному значению словаря вы можете использовать suppress следующим образом.

value = None
with suppress(KeyError):
    value = data['deeply']['nested']['dictionary']['key']
person Techdragon    schedule 25.08.2017
comment
Самое чистое и Pythonic-решение для одновременного извлечения нескольких строк вложенных ключей с использованием одного with suppress(KeyError): - person IODEV; 14.04.2021

Как вы относитесь к чему-то подобному

if 'caption' in post:
    caption = post['caption']['text']

Но он также начинает ломаться

if 'images' in post and 'standard_resolution' in post['images']:
    image_url = post['images']['standard_resolution']['url']

Поэтому я думаю, что самый питонический способ — это просто попросить прощения, а не разрешения

try:
    image_url = post['images']['standard_resolution']['url']
except KeyError:
    image_url = None
person Ric    schedule 23.02.2013
comment
Однако не используйте голые исключения, иначе произойдут забавные плохие вещи. (Например, KeyboardInterrupt проглотили) - person nneonneo; 23.02.2013

Я бы создал собственный подкласс dict, а затем просто обратился к этому:

class SafeDict(dict):
    def __getitem__(self,k):
        if k in self:
            return dict.__getitem__(self,k)
        return None


a = SafeDict({'a':'a'})
print a['a']
>> a
print a['b']
>> None

Вы можете либо выполнить пользовательскую init для обработки вложенных диктов как другого экземпляра SafeDict (что позволит вам передавать их), либо вы можете использовать тестирование (или блок try/except) для предотвращения KeyErrors

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

class AttributeSafeObject(object):

    def __init__(self,**kwargs):
        for key in kwargs:
            setattr(self,key,kwargs[key])

    def __getattr__(self, name):
        try:
            return object.__getattribute__(self,name)
        except AttributeError:
            return None

post = AttributeSafeObject({'a':'a'})
print post.a
>> a
print post.title
>> None
person Jonathan Vanasco    schedule 23.02.2013
comment
post dict исходит от simplejson, я не уверен, как заставить simplejson вернуть SafeDict или преобразовать стандартный dict в SafeDict. - person Kenny Winker; 23.02.2013
comment
Ваш код для __getitem__() будет проще, чем return self.get(k) (в основном вы переписываете метод get()). В любом случае, это отвечает на вопрос, потому что даже a = SafeDict({'a': SafeDict({'b': 'b'})}) не работает на a['c']['d'], и это проблема, которую нужно решить. - person Eric O Lebigot; 23.02.2013
comment
Если вы отступите на секунду... вы заметите, что вызов AnyClass(yourdict) действительно вызывает AnyClass.__init__ с dict как kwargs. Если вы наследуете класс от dict, эти kwargs становятся dict. если вы наследуете от объекта, вы можете повеселиться с init. лично я бы, вероятно, пошел с обозначением объекта. это значительно упрощает программирование API. - person Jonathan Vanasco; 23.02.2013
comment
@EOL хороший улов. я отметил в ответе, что я явно не включал рекурсию. Это просто идея, которую можно реализовать. - person Jonathan Vanasco; 23.02.2013