Você pode usar getduas vezes:
example_dict.get('key1', {}).get('key2')
Isso retornará Nonese um key1oukey2 existir não.
Observe que isso ainda pode gerar um AttributeErrorif example_dict['key1']existe, mas não é um dict (ou um objeto do tipo dict com um getmétodo). O try..exceptcódigo que você postou aumentaria uma TypeErrorvez seexample_dict['key1'] ser subscrito.
Outra diferença é que os try...exceptcurtos-circuitos imediatamente após a primeira tecla ausente. A cadeia de getchamadas não.
Se você deseja preservar a sintaxe, example_dict['key1']['key2']mas não deseja que ele aumente KeyErrors, use a receita Hasher :
class Hasher(dict):
# https://stackoverflow.com/a/3405143/190597
def __missing__(self, key):
value = self[key] = type(self)()
return value
example_dict = Hasher()
print(example_dict['key1'])
# {}
print(example_dict['key1']['key2'])
# {}
print(type(example_dict['key1']['key2']))
# <class '__main__.Hasher'>
Observe que isso retorna um Hasher vazio quando uma chave está ausente.
Como Hasheré uma subclasse, dictvocê pode usar um Hasher da mesma maneira que você poderia usar um dict. Todos os mesmos métodos e sintaxe estão disponíveis, os Hashers tratam as chaves ausentes de maneira diferente.
Você pode converter um regular dictem um Hasherassim:
hasher = Hasher(example_dict)
e converta a Hasherpara regular com dicta mesma facilidade:
regular_dict = dict(hasher)
Outra alternativa é ocultar a feiura em uma função auxiliar:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
Portanto, o restante do seu código pode ficar relativamente legível:
safeget(example_dict, 'key1', 'key2')